package com.meshnetics.gb.type.impl.custom;

import com.meshnetics.gb.type.DataType;
import com.meshnetics.gb.type.DataTypeID;
import com.meshnetics.gb.type.DataValue;
import com.meshnetics.gb.type.MandatoryFormatter;
import com.meshnetics.gb.type.impl.GlobalFinalTypesRegistry;
import com.meshnetics.gb.type.impl.StringUtils;

import java.nio.ByteBuffer;

/**
 * Abstract implementation for creating custom data types. Considers data type id to be null. Hides mandatory formatter
 * inside of self providing methods to format short/max log string as abstract methods of class. String
 * serialization/deserialization performed via byte[] representation of value. byte[] deserialization is implemented via
 * ByteBuffer one. Basic equals(Obejct), hashCode() and toString() implemenations.
 *
 * @author AKovylin
 * @author Pavlov Dmitry
 */
public abstract class CustomDataType implements DataType {

    private final String[] aliases;
    private final CustomDataMandatoryFormatter formatter = new CustomDataMandatoryFormatter(this);

    protected CustomDataType(String name) {
        this(new String[]{name});
    }

    protected CustomDataType(String[] aliases) {
        if (aliases == null) {
            throw new NullPointerException("aliases is null");
        }
        if (aliases.length == 0) {
            throw new IllegalArgumentException("aliases must contain atleast one element");
        }
        this.aliases = aliases;
        GlobalFinalTypesRegistry.INSTANCE.registerType(this);
    }

    protected CustomDataType(String name, boolean register) {
        if (name == null) {
            throw new NullPointerException("name is null");
        }
        aliases = new String[]{name};
        if (register) {
            GlobalFinalTypesRegistry.INSTANCE.registerType(this);
        }
    }

    public String getName() {
        return aliases[0];
    }

    public String[] getAliases() {
        return (String[]) aliases.clone();
    }

    public DataTypeID getDataTypeID() {
        return null;
    }

    public DataValue valueOf(String value) {
        return valueOf(StringUtils.recoverBytesFromHexString(value, 0, value.length()));
    }

    public DataValue valueOf(byte[] value) {
        ByteBuffer bb = ByteBuffer.wrap(value);
        DataValue res = deserialize(bb);
        if (bb.remaining() != 0) {
            throw new IllegalArgumentException("invalid value - bytes left in array after parsing finished");
        }
        return res;
    }

    public boolean equals(Object obj) {
        return obj != null && getClass() == obj.getClass() &&
                this.aliases[0].equals(((CustomDataType) obj).aliases[0]);
    }

    public int hashCode() {
        return aliases[0].hashCode();
    }

    public String toString() {
        return StringUtils.getSimpleName(getClass()) + '(' + getName() + ')';
    }

    public MandatoryFormatter getMandatoryFormatter() {
        return formatter;
    }

    protected String formatSerializedString(CustomDataValue value) {
        return StringUtils.toHexString(value.bytesValue());
    }

    protected abstract String formatShortLogString(CustomDataValue value);

    protected abstract String formatMaxLogString(CustomDataValue value);

    protected class CustomDataMandatoryFormatter extends MandatoryFormatter {
        public CustomDataMandatoryFormatter(CustomDataType dataType) {
            super(dataType);
        }

        public DataValue valueOf(String serializedValue) {
            return CustomDataType.this.valueOf(serializedValue);
        }

        public String formatSerializedString(DataValue value) {
            checkValue(value);
            return CustomDataType.this.formatSerializedString((CustomDataValue) value);
        }

        public String formatShortLogString(DataValue value) {
            checkValue(value);
            return CustomDataType.this.formatShortLogString((CustomDataValue) value);
        }

        public String formatMaxLogString(DataValue value) {
            checkValue(value);
            return CustomDataType.this.formatMaxLogString((CustomDataValue) value);
        }

        private void checkValue(DataValue value) {
            if (value == null) {
                throw new NullPointerException("value is null");
            }
            if (!(value instanceof CustomDataValue)) {
                throw new IllegalArgumentException("unsupported value class " + value.getClass());
            }
        }
    }
}
